﻿#region header

//  ***************************************************************************
// *                                LICENSE
//  ***************************************************************************
//  * "Haplous" .NET Simple 2D Game Framework
//  * 
//  * Copyright © Jonathan Evans 2014-2014
//  * 
//  * This program is free software; you can redistribute it and/or
//  * modify it under the terms of the GNU General Public License
//  * as published by the Free Software Foundation; either version 2
//  * of the License, or (at your option) any later version.
//  * 
//  * This program is distributed in the hope that it will be useful,
//  * but WITHOUT ANY WARRANTY; without even the implied warranty of
//  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//  * GNU General Public License for more details.
//  * 
//  * You should have received a copy of the GNU General Public License
//  * along with this program; if not, write to the Free Software
//  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
//  * 
//  ***************************************************************************
//  * 
//  * Contact: opencollar@googlemail.com
//  * 
//  * Website: http://haplous.codeplex.com/
//  *
//  * NuGet:   http://www.nuget.org/packages/haplous/
//  *
//  * File:    Haplous/Ghosthunt/Ghost.cs
//  *
//  ***************************************************************************

#endregion

using System;
using System.Threading;

using Ghosthunt.Resources;

using Haplous;
using Haplous.Core;

namespace Ghosthunt
{
    /// <summary>
    ///     A class representing a ghost and its behavior.
    /// </summary>
    internal class Ghost : Actor
    {
        /// <summary>
        ///     Temporary storage for the current direction when the mode changes.
        /// </summary>
        private DirectionKind _currentDirection;

        /// <summary>
        ///     Initializes a new instance of the <see cref="ElementBase" /> class.
        /// </summary>
        /// <param name="game">
        ///     The game to which the class belongs.  The <paramref name="game" /> must not be
        ///     <see langword="null" />.
        /// </param>
        /// <param name="color">The color of the ghost.</param>
        /// <param name="initialDirection">The initial direction in which to move.</param>
        /// <param name="huggingWall">
        ///     The direction, relative to the direction of travel, to which we should always keep a wall.
        ///     Must be either <see cref="DirectionKind.Left" /> or <see cref="DirectionKind.Right" />.
        /// </param>
        public Ghost(Game game, string color, DirectionKind initialDirection, DirectionKind huggingWall) : base(game, color + "Ghost")
        {
            game.ActiveMode = game.Modes["Normal"];

            // Get the static pictures that will be used for the animation
            var pic1 = game.AddPicture(color + "Ghost-01", ResourcesHelper.GetBitmap("Ghost_" + color + "Static"));
            var pic2 = game.AddPicture(color + "Ghost-02", ResourcesHelper.GetBitmap("Ghost_" + color + "Moving01"));
            var pic3 = game.AddPicture(color + "Ghost-03", ResourcesHelper.GetBitmap("Ghost_" + color + "Moving02"));

            // Now create the animated picture that will be used in the game.
            var timerSync = game.Cues["Animation"];
            var animation = new SimplePictureSequence(game, color + "Ghost", timerSync, IterationKind.LoopLeftToRight);
            animation.AddPictures(pic1, pic2, pic3);
            game.PictureSequences.Add(animation);

            timerSync.Tick += UpdateOpacity;

            // And set it to be our picture
            PictureSource.Source = animation;
            Visualization.Visible = true;
            Visualization.Origin = OriginKind.Center;
            Visualization.Scaling = PictureScalingKind.Stretch;
            Visualization.Tiling = TileKind.None;

            Size.Set(64, 64);

            Director = new GhostDirector(game, "Normal" + color + "GhostController", this, game.Cues["Animation"], initialDirection, huggingWall);

            // Now make sure that the appearance and behaviour when in "Ghost Hunting" mode matches normal mode
            CopyModeProperties(game.Modes["Normal"], game.Modes["GhostHunt"], new[] {"Director"});

            // And make the controller different (we follow a different route)
            game.ActiveMode = game.Modes["GhostHunt"];
            // TODO: Different director
            Director = new GhostDirector(game, "GhostHunt" + color + "GhostController", this, game.Cues["Animation"], initialDirection, huggingWall);
        }

        /// <summary>
        ///     Updates the opacity of the ghost at regular intervals, based upon the current mode.
        /// </summary>
        /// <param name="sender">The sender.</param>
        /// <param name="e">The <see cref="EventArgs" /> instance containing the event data.</param>
        private void UpdateOpacity(object sender, EventArgs e)
        {
            if(!Game.IsPlaying)
                return;

            var mode = Game.ActiveMode;

            if(mode == null)
                return;

            double opacity = 0;

            switch(mode.Name)
            {
                case "Normal":
                    opacity = 0.25 + (Waves.SineWave(0.5) / 3d);
                    break;

                case "GhostHunt":
                    opacity = 0.25 + (Waves.SquareWave(2) * 0.75d);
                    break;
            }

            Opacity.Value = opacity;
        }

        /// <summary>
        ///     Called when the active mode is about to change.
        /// </summary>
        /// <param name="previousMode">The previous mode.</param>
        /// <param name="newMode">The new mode.</param>
        /// <exception cref="System.NotImplementedException"></exception>
        protected override void OnModeChanging(Mode previousMode, Mode newMode)
        {
            if(Director != null)
                _currentDirection = Director.CurrentDirection.Value;

            base.OnModeChanging(previousMode, newMode);
        }

        /// <summary>
        ///     Called when the active mode has changed.
        /// </summary>
        /// <param name="previousMode">The previous mode.</param>
        /// <param name="newMode">The new mode.</param>
        protected override void OnModeChanged(Mode previousMode, Mode newMode)
        {
            if(previousMode == null)
                return;

            CopyModeProperties(previousMode, newMode, new[] {"Director", "Opacity"});

            if(Director != null)
                Director.CurrentDirection.Value = _currentDirection;
        }

        /// <summary>
        ///     Occurs when an item that is attempting to move is found to be in collision with this item.
        /// </summary>
        /// <param name="item">The item that is attempting to move.</param>
        /// <param name="oldPosition">The starting position of the item that has changed.</param>
        /// <param name="oldSize">The original size of the item that has changed.</param>
        /// <param name="newPosition">The proposed new position of the item that has changed.</param>
        /// <param name="newSize">The proposed new size of the item that has changed.</param>
        /// <returns>The outcome of the collision.</returns>
        /// <remarks>
        ///     The intention of this method is to allow the receiver to make a decision about, but not yet react, to an impact.
        ///     The reaction should occur when the <see cref="SolidGraphicBlock.OnCollision" /> method is called.
        /// </remarks>
        public override OutcomeKind OnColliding(SolidGraphicBlock item, PositionInt32 oldPosition, SizeInt32 oldSize, PositionInt32 newPosition, SizeInt32 newSize)
        {
            if(item is Wall)
                return OutcomeKind.Bounce;

            // We can just plough over ghosts in ghost hunt mode.
            if(((Ghosthunt)Game).InGhostHuntMode)
            {
                if(item is Ghosthunter)
                {
                    // We're in trouble now!
                    return OutcomeKind.Reactive;
                }
            }

            return OutcomeKind.Permitted;
        }

        /// <summary>
        ///     Called when another element has moved and a collision has occurred.
        /// </summary>
        /// <param name="item">The item that has moved.</param>
        /// <param name="anticipatedOutcome">
        ///     The anticipated outcome from the previous call to
        ///     <see cref="SolidGraphicBlock.OnColliding" />.
        /// </param>
        /// <remarks>This method is called to allow the recipient to react to the collision.</remarks>
        public override void OnCollision(SolidGraphicBlock item, OutcomeKind anticipatedOutcome)
        {
            // We can just plough over ghosts in ghost hunt mode.
            var game = (Ghosthunt)Game;
            if(game.InGhostHuntMode)
            {
                if(item is Ghosthunter)
                {
                    // We're in trouble now!
                    game.GhostEaten();

                    // Go back to our starting point.
                    ThreadPool.QueueUserWorkItem(x =>
                                                 {
                                                     // A little delay to make sure we have finished updating,
                                                     // otherwise there will be an error.
                                                     Thread.Sleep(10);
                                                     Reset(); // go back to our starting position
                                                 });
                }
            }
        }
    }
}